package dyyx.util;

import java.lang.management.ManagementFactory;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;

import javax.management.Attribute;
import javax.management.AttributeList;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanInfo;
import javax.management.MBeanServer;
import javax.management.MBeanServerConnection;
import javax.management.ObjectInstance;
import javax.management.ObjectName;
import javax.management.openmbean.CompositeData;
import javax.management.openmbean.CompositeDataSupport;
import javax.management.openmbean.CompositeType;

import org.apache.commons.lang3.StringUtils;


public abstract class JmxUtil{
	
	private static final MBeanServer server = ManagementFactory.getPlatformMBeanServer();
	
	
	private static final Set<String> skips = new HashSet<String>();
	
	private static Set<String> ignoreMbeanAttrs;
	
	
	
	static{
		skips.add("SystemProperties");
		skips.add("AllThreadIds");
		skips.add("ObjectName");
		// DiagnosticOptions
		skips.add("DiagnosticOptions");

		//
		Set<String> set = new HashSet<String>();
    	set.add("Name");
    	set.add("ObjectName");
    	
    	ignoreMbeanAttrs = Collections.unmodifiableSet(set);
		
		
	}
	
	
	/**
	 * 
	 * @param hostAndPort  127.0.0.1:8888
	 * @return
	 */
	public static String buildJmxServiceUrl(String hostAndPort){
		// service:jmx:rmi:///jndi/rmi://127.0.0.1:9990/jmxrmi
		String url = "service:jmx:rmi:///jndi/rmi://"+hostAndPort+"/jmxrmi";
		return url;
	}
	
	public static Set<ObjectName> query(String q)throws Exception{
		return query(server,q);
	}

	public static Set<String> queryAsString(String q)throws Exception{
		return queryAsString(server,q);
	}
	
	
	public static Map<String,Map<String, Object>> getMBeanAttributeValuesMap(MBeanServerConnection mbeanServerConnection,Set<String> names)throws Exception{
		if(names==null || names.isEmpty()){
			return null;
		}
		Map<String,Map<String,Object>> map = new TreeMap<>();
		//JmxUtil.getMBeanInfoMap(names);
		for(String item:names){
			try{
			    map.put(item, JmxUtil.getMBeanAttributeValues(mbeanServerConnection, item));
			}catch(Throwable e){
				RunStatusUtil.logError("getMBeanAttributeValues error", e,item);
			}
		}
		return map;	
	}	
	
	public static Map<String, Object> getMBeanAttributeValues(String obejctName)throws Exception{
		return getMBeanAttributeValues(server,obejctName);
	}
	
	
	
	

	public static Map<String, Object> getMBeanAttributeValues(MBeanServerConnection mbeanServerConnection,
			String obejctName) throws Exception {
		ObjectName objectName = new ObjectName(obejctName);
		MBeanInfo mbeaninfo = mbeanServerConnection.getMBeanInfo(objectName);

		MBeanAttributeInfo[] attrs = mbeaninfo.getAttributes();
		if (attrs == null) {
			return null;
		}
		List<String> attrNameList = new ArrayList<String>();
		String attrName = null;
		for(MBeanAttributeInfo item:attrs){
			attrName = item.getName();
			if(ignoreMbeanAttrs.contains(attrName)){
				continue;
			}
			attrNameList.add(attrName);
		}
        // java.rmi.UnmarshalException: error unmarshalling return
		// java.io.WriteAbortedException: writing aborted; java.io.NotSerializableException
		// 20200819
		AttributeList attrlist = mbeanServerConnection.getAttributes(objectName, attrNameList.toArray(new String[0]));

		List<Attribute> alist = attrlist.asList();
		Map<String, Object> amap = new TreeMap<String, Object>();
		String key = null;
		Object value = null;
		for (Attribute item : alist) {
			key = item.getName();
			value = item.getValue();
			if (key == null || value == null) {
				continue;
			}

			if (ignoreMbeanAttrs.contains(key)) {
				continue;
			}

			if (value instanceof CompositeData) {
				CompositeData cd = (CompositeData) value;
				CompositeType ct = cd.getCompositeType();
				// content keys
				Set<String> cdkeys = ct.keySet();

				// CompositeDataSupport

				Map<String, Object> cdmap = new TreeMap<String, Object>();
				for (String cdkey : cdkeys) {
					cdmap.put(cdkey, cd.get(cdkey));
				}
				// TODO  CompositeDataSupport  getCompositeDataValue  

				amap.put(key, cdmap);
			} else {
				amap.put(key, value);
			}
			// amap.put(key, value.toString());
		}
		return amap;
	}

	
	public static Set<ObjectName> query(MBeanServerConnection mbeanServerConnection,String q)throws Exception{
		if(StringUtils.isBlank(q)){
			q = "*:*";
		}
		Set<ObjectInstance> list = mbeanServerConnection.queryMBeans(new ObjectName(q), null);
		Set<ObjectName> list2 = new TreeSet<ObjectName>();
		
		for(ObjectInstance item:list){
			list2.add(item.getObjectName());
		}
		return list2;	
	}
	
	public static Set<String> queryAsString(MBeanServerConnection mbeanServerConnection,String q)throws Exception{
		Set<ObjectName> objectNames = query(mbeanServerConnection,q);
		
		Set<String> names = new TreeSet<String>();
		String name = null;
		for(ObjectName item:objectNames){
			// canonicalKeyPropertyListString 按 key 排序 
			// name = item.getDomain()+":"+item.getCanonicalKeyPropertyListString();
			// 按 type 分类 ，分类相同的放在一起
			name = item.getDomain()+":"+item.getKeyPropertyListString();
			names.add(name);
		}
		
		return names;
	}
	
	
	
	
	
	
	
	public static long getPid(){
		String name = ManagementFactory.getRuntimeMXBean().getName();  
		String str = CommUtil.subStringBefore(name, "@");
		if(str==null){
			return 0;
		}
		return CommUtil.getLong(str);
	}
	
	public static Map<String,Set<String>> getAllObjectNameAndAttributes(){
		Set<ObjectInstance> instances = getMBeans();
		
		Map<String,Set<String>> map = new TreeMap<String,Set<String>>();
		
		MBeanInfo mbeanInfo = null;
		ObjectName objectName = null;
		
		// Map<String,Object> submap = null;
		
		MBeanAttributeInfo[] attributeInfos = null;
		
        for(ObjectInstance item:instances){
        	objectName = item.getObjectName();
        	
        	try{
        	    mbeanInfo = server.getMBeanInfo(objectName);
        	    attributeInfos = mbeanInfo.getAttributes();
        	}catch(Throwable e){
        		//
        		
        	}
        	
        	
        	// map.put(objectName.get, submap);
        }	
		
		
		return map;

	}


	public static Set<ObjectInstance> getMBeans(){
		
        
        return server.queryMBeans(null, null);
	}
	
	
	public static MBeanInfo getMBeanInfo(String name)throws Exception{
		if(CommUtil.isBlank(name)){
			return null;
		}
		ObjectName objectName = new ObjectName(name);
		return server.getMBeanInfo(objectName);
	}

	
	public static List<MBeanInfo> getMBeanInfos()throws Exception{
		Set<ObjectInstance> instances = getMBeans();
		List<MBeanInfo> list = new ArrayList<MBeanInfo>();
        for(ObjectInstance item:instances){
        	list.add(server.getMBeanInfo(item.getObjectName()));
        }	
        
        return list;	
	}
	
	public static Map<String,Map<String,Object>> getMBeanInfoMap()throws Exception{
		Set<ObjectInstance> instances = getMBeans();
		// List<MBeanInfo> list = new ArrayList<MBeanInfo>();
		
		Map<String,Map<String,Object>> map = new TreeMap<String,Map<String,Object>>();
		
		MBeanInfo mbeanInfo = null;
		ObjectName objectName = null;
		
		Map<String,Object> submap = null;
		
        for(ObjectInstance item:instances){
        	objectName = item.getObjectName();
        	
        	try{
        	    mbeanInfo = server.getMBeanInfo(objectName);
        	    submap = getMBeanInfoMap(objectName,mbeanInfo);
        	}catch(Throwable e){
        		//
        		
        	}
        	
        	if(submap==null){
        		continue;
        	}
        	map.put(item.toString(), submap);
        }	
        
        return map;	
	}
	
	
	public static Map<String,Map<String,Object>> getMBeanInfoMap(Set<String> names)throws Exception{
		if(names==null || names.isEmpty()){
			return null;
		}
		Map<String,Map<String,Object>> map = new TreeMap<String,Map<String,Object>>();
		
		MBeanInfo mbeanInfo = null;
		ObjectName objectName = null;
		
		Map<String,Object> submap = null;
		
        for(String name:names){
        	objectName = new ObjectName(name);
        	
        	try{
        	    mbeanInfo = server.getMBeanInfo(objectName);
        	    submap = getMBeanInfoMap(objectName,mbeanInfo);
        	}catch(Throwable e){
        		//
        		// System.out.println("getMBeanInfoMap error,"+e);
        	}
        	
        	if(submap==null){
        		continue;
        	}
        	map.put(name, submap);
        }	
        
        return map;	
	}
	
	private static Map<String,Object> getMBeanInfoMap(ObjectName objectName,MBeanInfo mbeanInfo)throws Exception{
		if(mbeanInfo==null){
			return null;
		}
		MBeanAttributeInfo[] attrs = mbeanInfo.getAttributes();
		if(attrs==null || attrs.length<=0){
			return null;
		}
	    String[] attrNames =  getAttributeNames(attrs);
	    if(attrNames==null || attrNames.length<=0){
	    	return null;
	    }
	    
	    AttributeList alist = server.getAttributes(objectName, attrNames);
	    int size = 0;
	    if(alist!=null){
	    	size = alist.size();
	    }
	    if(size<=0){
	    	return null;
	    }
	    
	    List<Attribute> attrList = alist.asList();
	    
	    if(attrList==null || attrList.isEmpty()){
	    	return null;
	    }
	    
	    
		String name = null;
		Object value = null;
		Map<String,Object> map = new TreeMap<String,Object>();
		
		for(Attribute item:attrList){
			name = item.getName();
			value = item.getValue();
			//
			if(value instanceof CompositeDataSupport){
				// CompositeDataSupport cds = (CompositeDataSupport)value;
				value = getCompositeDataValue(value);
				map.put(name, value);

			}else{
				map.put(name, value);
			}
			
		}
		
		/*
		for(MBeanAttributeInfo item:attrs){
			name = item.getName();
			if(skips.contains(name)){
				continue;
			}
			value = server.getAttribute(objectName, name);
			if(value!=null){
				
				map.put(name, value);
				
				//if(typeCheck(value)){
				//	map.put(name, value);
				//}else{
					//map.put(name, value.toString());
				//}
					
			}
		}
		*/
		
		return map;
		
	}
	
	private static Object getCompositeDataValue(Object value){
		if(value==null){
			return null;
		}
		
		CompositeDataSupport cds = (CompositeDataSupport)value;
		
		try{
		    Field field = CompositeDataSupport.class.getDeclaredField("contents");
		    field.setAccessible(true);
		    value = field.get(cds);
		}catch(Throwable e){
			value = null;
			// System.out.println("getCompositeDataValue error,"+e);
		}
		
		// System.out.println(value.getClass());
		
		return value;
	}
	
	private static String[] getAttributeNames(MBeanAttributeInfo[] attrs){
		if(attrs==null ||attrs.length<=0){
			return null;
		}
        List<String> list = new ArrayList<String>();
        String name = null;
		for(MBeanAttributeInfo item:attrs){
			name = item.getName();
			if(skips.contains(name)){
				continue;
			}
			list.add(name);
		}
		return list.toArray(new String[0]);
	}
	
	
	public static Map<String,Object> getSysMbeans(){
		Map<String,Object> map = new HashMap<String,Object>();
		
		map.put("classLoadingMXBean", ManagementFactory.getClassLoadingMXBean());
		map.put("compilationMXBean", ManagementFactory.getCompilationMXBean());
		map.put("garbageCollectorMXBeans", ManagementFactory.getGarbageCollectorMXBeans());
		map.put("memoryManagerMXBeans", ManagementFactory.getMemoryManagerMXBeans());
		map.put("memoryMXBean", ManagementFactory.getMemoryMXBean());

		map.put("memoryPoolMXBeans", ManagementFactory.getMemoryPoolMXBeans());
		map.put("operatingSystemMXBean", ManagementFactory.getOperatingSystemMXBean());

		
		return map;
	}
	
	
	private static boolean typeCheck(Object obj){
		// 复杂类型 fastjson 序列化 会报 stackoverflow 
		if(obj instanceof Boolean  || obj instanceof Number || obj instanceof String ){
			return true;
		}
		return false;
		
	}
	
	

	public static boolean match(String objectName,String name){
		if(CommUtil.isBlank(objectName) || CommUtil.isBlank(name) ){
			return false;
		}
		if(objectName.contains(name)){
			return true;
		}
		
		return false;
	}
	
	public static Set<String> matchedSet(Set<String> set,String name){
		if(set==null || set.isEmpty() || CommUtil.isBlank(name)){
			return set;
		}
		 Set<String> set2 = new TreeSet<>();
		for(String item:set){
			if(match(item,name)){
				set2.add(item);
			}
		}
		return set2;
	}
	
}
